/**
 * Class: Timebase
 *
 * Purpose: 
 *
 * Created: Feb 18, 2010 @ 2:38:03 PM
 * @author Ken Scott
 */
package org.openfantasia.time;

import org.openfantasia.util.DateTime;

import java.util.ArrayList;
import java.util.List;

public class Timebase extends Thread implements TimeKeeper {

	private long timeNS = 0;

	private long timeUS = 0;

	private long timeMS = 0;

	private double timeS = 0;

	private long startTimeNS = Long.MIN_VALUE;

	private long endTimeNS = Long.MIN_VALUE;

	private double speed = 1.0;

	private boolean started = false;

	private boolean running = false;

	private boolean halted = false;

	private long prevTime = Long.MIN_VALUE;

	private final List<Ticker> tickers = new ArrayList<Ticker>();


	public Timebase() {
	}


	public Timebase(double speed) {
		this.speed = speed;
	}


	public Timebase(double startTimeS, double endTimeS) {
		reset(startTimeS, endTimeS);
	}


	public Timebase(long startTimeNS, long endTimeNS) {
		reset(startTimeNS, endTimeNS);
	}


	public void run() {
		while (!halted) {
			synchronized (this) {
				if (running && !halted) {
					if (timeNS < startTimeNS) {
						setTime(startTimeNS);
						DateTime.waitMicroseconds(50);
					}
					else if (timeNS > endTimeNS) {
						setTime(endTimeNS);
						DateTime.waitMicroseconds(50);
					}
					else {
						long now = System.nanoTime();
						long delta = now - prevTime;
						prevTime = now;
						advance((long) (speed * (double) delta));

						for (Ticker ticker : tickers) {
							ticker.tick(timeNS);
						}
					}
				}
				else {
					DateTime.waitMicroseconds(1000);
				}
			}
		}
	}


	private void advance(long delta) {
		setTime(timeNS + delta);
	}


	private void setTime(long ns) {
		this.timeNS = ns;
		timeUS = (long) ((double) ns / 1e3);
		timeMS = (long) ((double) ns / 1e6);
		timeS = (double) ns / 1e9;
	}


	public void start() {
		if (!started) {
			prevTime = Long.MIN_VALUE;
			setTime((startTimeNS != Long.MIN_VALUE) ? startTimeNS : 0);
			started = true;

			super.start();
		}

		synchronized (tickers) {
			tickers.notifyAll();
		}
	}


	public void reset() {
		prevTime = Long.MIN_VALUE;
		setTime((startTimeNS != Long.MIN_VALUE) ? startTimeNS : 0);
	}


	public void reset(double startTimeS, double endTimeS) {
		reset((long) (startTimeS * 1e9), (long) (endTimeS * 1e9));
	}


	public void reset(long startTimeNS, long endTimeNS) {
		synchronized (tickers) {
			prevTime = Long.MIN_VALUE;
			this.startTimeNS = startTimeNS;
			this.endTimeNS = endTimeNS;
			setTime((startTimeNS != Long.MIN_VALUE) ? startTimeNS : 0);
			tickers.notifyAll();
		}
	}


	public void toStart() {
		setTime((startTimeNS != Long.MIN_VALUE) ? startTimeNS : 0);
	}


	public void toEnd() {
		setTime((endTimeNS != Long.MIN_VALUE) ? endTimeNS : Long.MAX_VALUE);
	}


	public boolean isRunning() {
		return running;
	}


	public void setRunning(boolean running) {
		if (running && !started) {
			start();
		}

		synchronized (tickers) {
			this.running = running;
			prevTime = System.nanoTime();
			tickers.notifyAll();
		}
	}


	public void halt() {
		synchronized (tickers) {
			halted = true;
			prevTime = System.nanoTime();
			tickers.notifyAll();
		}
	}


	public void setSpeed(double speed) {
		this.speed = speed;
	}


	public double getSpeed() {
		return speed;
	}


	public void addTicker(Ticker t) {
		synchronized (tickers) {
			if (!tickers.contains(t)) {
				t.setTimebase(this);
				tickers.add(t);
				tickers.notifyAll();
			}
		}
	}


	public void removeTicker(Ticker t) {
		synchronized (tickers) {
			if (tickers.remove(t)) {
				t.setTimebase(null);
			}
		}
	}


	public void setTime(double time) {
		synchronized (this) {
			prevTime = System.nanoTime();
			setTime((long) (time * 1e9));
		}
	}


	public double getTime() {
		return timeS;
	}


	public long getTimeMS() {
		return timeMS;
	}


	public long getTimeUS() {
		return timeUS;
	}


	public long getTimeNS() {
		return timeNS;
	}


	public long getStartTimeNS() {
		return startTimeNS;
	}


	public double getStartTime() {
		return (double) startTimeNS / 1e9;
	}


	public long getEndTimeNS() {
		return endTimeNS;
	}


	public double getEndTime() {
		return (double) endTimeNS / 1e9;
	}
}
